/**
 * 
 */
package systole.processor;

import java.math.BigDecimal;
import systole.domain.analysis.results.AnalysisResult;
import systole.domain.analysis.results.ParameterAOD;
import systole.domain.analysis.results.ParameterAOS;
import systole.domain.analysis.results.ParameterCoord;
import systole.domain.analysis.results.ParameterIAR;
import systole.domain.analysis.results.ParameterT;
import systole.domain.signals.FinalSignal;
import systole.domain.signals.Segment;

/**
 * Class to calculate final parameters
 * 
 * @author jmj
 * 
 *
 */
public class SignalAnalyzer {

    private static final BigDecimal FIFTY = new BigDecimal(50);
    private static final int BEGIN_OF_WINDOW_TIME_FOR_DIASTOLE = 20;
    private static final int END_OF_WINDOW_TIME_FOR_DIASTOLE = 40;
    private int posOfMax;
    private int posBeginigDiastole;
    private int posOfIAR;
    private float heartRate;
    private BigDecimal k;
    private FinalSignal finalSignal;

    /**
     *
     * @param frequency
     * @param finalSignal
     * @param heartRate
     */
    public SignalAnalyzer(BigDecimal frequency, FinalSignal finalSignal, float heartRate) {
        super();
        this.finalSignal = finalSignal;
        this.calculatePoints();
        //Período de muestreo
        this.k = frequency;
        this.heartRate = heartRate;
    }

    private void calculatePoints() {
        // obtain max value, must be value 100, index 50
        this.posOfMax = this.finalSignal.getFinalSegment().getPosOfMax(); //Obtiene el valor en X del maximo
        this.posBeginigDiastole = this.posOfMax + BEGIN_OF_WINDOW_TIME_FOR_DIASTOLE
                + this.finalSignal.getSecondDerivative().getPosOfMax(this.posOfMax + BEGIN_OF_WINDOW_TIME_FOR_DIASTOLE, this.posOfMax + END_OF_WINDOW_TIME_FOR_DIASTOLE);
    }

    private int obtainIntersection(Segment firstSegment, Segment SecondSegment, int init) {
        int intersection = init;
        Segment majorSegment;
        Segment minorSegment;
        if ((init >= firstSegment.size()) || (init >= SecondSegment.size())) {
            return init;
        }
        if ((firstSegment.elementAt(init).doubleValue()) > (SecondSegment.elementAt(init).doubleValue())) {
            majorSegment = firstSegment;
            minorSegment = SecondSegment;
        } else {
            minorSegment = firstSegment;
            majorSegment = SecondSegment;
        }

        while (((majorSegment.size() > intersection) && (minorSegment.size() > intersection)) && (majorSegment.elementAt(intersection).doubleValue() > minorSegment.elementAt(intersection).doubleValue())) {
            intersection++;
        }

        return intersection;
    }

    @SuppressWarnings("unused")
    private int obtainIntersectionDownTo(Segment firstSegment, Segment SecondSegment, int endPos) {
        int intersection = endPos;
        Segment majorSegment;
        Segment minorSegment;
        if ((endPos >= firstSegment.size()) || (endPos >= SecondSegment.size())) {
            return endPos;
        }
        if ((firstSegment.elementAt(endPos).doubleValue()) > (SecondSegment.elementAt(endPos).doubleValue())) {
            majorSegment = firstSegment;
            minorSegment = SecondSegment;
        } else {
            minorSegment = firstSegment;
            majorSegment = SecondSegment;
        }

        while (((majorSegment.size() > intersection) && (minorSegment.size() > intersection)) && (majorSegment.elementAt(intersection).doubleValue() > minorSegment.elementAt(intersection).doubleValue())) {
            intersection--;
        }

        return intersection;
    }

    private ParameterIAR calculateIAR() {
        //int posOfMaxFirstDerivative = this.firsDerivatite.getPosOfMax(this.posOfMax, this.posBeginigDiastole);//Coordenada en X del maximo derivada 1
        int posOfMaxFirstDerivative = this.finalSignal.getSecondDerivative().getPosOfMin(this.posOfMax, this.posBeginigDiastole);
        ParameterIAR param = new ParameterIAR();
        //param.setInitPos(0f);
        // if distance is zero, search from other postion
        if (posOfMaxFirstDerivative == 0) {
            int half = (this.posBeginigDiastole - this.posOfMax) / 2;
            posOfMaxFirstDerivative = half + this.finalSignal.getSecondDerivative().getPosOfMin(this.posOfMax + half, this.posBeginigDiastole);
        }

        posOfMaxFirstDerivative += this.posOfMax;

        posOfMaxFirstDerivative = this.posOfMax + this.finalSignal.getFourthDerivative().looksFirstPositionOfNearestValueOnSubSegmentDowTo(FIFTY, posOfMax, posOfMaxFirstDerivative);
        // previo a la linea superiro 30/11/11
        // posOfMaxFirstDerivative = this.obtainIntersectionDownTo(this.finalSignal.getSecondDerivative(), this.finalSignal.getFourthDerivative(), posOfMaxFirstDerivative);



        param.setInitPos(new ParameterCoord(this.k.multiply(new BigDecimal(posOfMaxFirstDerivative)), BigDecimal.ZERO));
        param.setEndPos(new ParameterCoord(this.k.multiply(new BigDecimal(posOfMaxFirstDerivative)), this.finalSignal.getFinalSegment().elementAt(posOfMaxFirstDerivative)));
        //param.setEndPos(new Float(this.posOfMax + posOfMaxFirstDerivative));        
        this.posOfIAR = posOfMaxFirstDerivative;
        return (param);
    }

    private ParameterAOD calculateAOD() {

        // obtengo la intersección de las derivadas para limitar la sección donde buscar el minimo
        int posOfIntersection = this.obtainIntersection(this.finalSignal.getFirstDerivatite(), this.finalSignal.getSecondDerivative(), this.posBeginigDiastole);

        // obtengo el minimo entre el inicio de la diastole y el cruce de la priemra y segunda derivada
        int firstMinInDiastole = this.finalSignal.getFinalSegment().getPosOfMin(this.posBeginigDiastole, posOfIntersection);

        // obtengo el siguiente cruce para limitar hasta donde puedo bscar el maximo
        int secondIntersection = this.obtainIntersection(this.finalSignal.getFirstDerivatite(), this.finalSignal.getSecondDerivative(), posOfIntersection + 1);

        // buco el maximo entre las dos intersecciones
        int posOfMaxInDiastole = this.finalSignal.getFinalSegment().getPosOfMax(posOfIntersection, secondIntersection);

        // llevo todo a posiciones reales
        posOfMaxInDiastole += posOfIntersection + 1;
        firstMinInDiastole += this.posBeginigDiastole;

        // si es negativa puede ser que queden muy pegadas siendo malo el calculo
        // entonces corrige
        if ((this.finalSignal.getFinalSegment().get(firstMinInDiastole).floatValue() > this.finalSignal.getFinalSegment().get(posOfMaxInDiastole).floatValue()) && ((posOfMaxInDiastole - firstMinInDiastole) < 10)) {

            firstMinInDiastole = this.posBeginigDiastole + ((posOfIntersection - this.posBeginigDiastole) / 2);
            posOfMaxInDiastole = posOfIntersection + 1 + this.finalSignal.getSecondDerivative().getPosOfMin(posOfIntersection, this.posOfMax + END_OF_WINDOW_TIME_FOR_DIASTOLE);
        }

        ParameterAOD param = new ParameterAOD();

        this.posBeginigDiastole = firstMinInDiastole;
        param.setInitPos(new ParameterCoord(this.k.multiply(new BigDecimal(firstMinInDiastole)), this.finalSignal.getFinalSegment().get(firstMinInDiastole)));
        param.setEndPos(new ParameterCoord(this.k.multiply(new BigDecimal(posOfMaxInDiastole)), this.finalSignal.getFinalSegment().get(posOfMaxInDiastole)));

        return (param);
    }

    /**
     * Calculate AOS at 50%
     *
     * @return longitud of OS at 50%
     */
    private ParameterAOS calculateAOS() {

        Integer posOfFirstFifty = this.finalSignal.getFinalSegment().looksPositionOfNearestValueOnSubSegment(SignalAnalyzer.FIFTY, 0, this.posOfMax);

        Integer posOfNextFifty = this.posOfMax + this.finalSignal.getFinalSegment().
                looksFirstPositionOfNearestValueOnSubSegment(SignalAnalyzer.FIFTY, this.posOfMax, this.finalSignal.getFinalSegment().size() - 2);

        if (posOfNextFifty > this.posBeginigDiastole) {
            posOfNextFifty = this.doExponential();
        }

        //Multiplico por el periodo de muestreo para que me escale de "muestras" a "milisegundos"
        BigDecimal xPos = this.k.multiply(new BigDecimal(posOfFirstFifty));
        BigDecimal yPos = this.k.multiply(new BigDecimal(posOfNextFifty));

        ParameterAOS param = new ParameterAOS();

        param.setInitPos(new ParameterCoord(xPos, SignalAnalyzer.FIFTY));
        param.setEndPos(new ParameterCoord(yPos, SignalAnalyzer.FIFTY));

        return (param);
    }

    /**
     *
     *
     * @return time between OS and RS
     */
    private ParameterT calculateT() {
        ParameterT param = new ParameterT();
        // primer punto, toma el máximo (sistole)
        param.setInitPos(new ParameterCoord(this.k.multiply(new BigDecimal(this.posOfMax)), this.finalSignal.getFinalSegment().elementAt(this.posOfMax)));

        // segundo punto, toma el maximo de la segunda derivada entre la sistole y el RS
        int lastPoint = this.posOfMax + this.finalSignal.getSecondDerivative().getPosOfMax(this.posOfMax, this.posOfIAR);
        param.setEndPos(new ParameterCoord(this.k.multiply(new BigDecimal(lastPoint)), this.finalSignal.getFinalSegment().elementAt(lastPoint)));
        return (param);
    }

    private int doExponential() {
        int pos = this.posOfMax + ((this.posBeginigDiastole - this.posOfMax) / 2);
        boolean lower = false;
        double value;
        double exp;
        while ((pos < this.finalSignal.getFinalSegment().size()) && (!lower)) {
            value = (this.finalSignal.getFinalSegment().get(pos).doubleValue() * 2.7f);
            exp = ((this.posOfIAR - pos) / 21.0f) * (-1.0f);
            value = Math.pow(value, exp);
            lower = value >= FIFTY.doubleValue();
            pos += (lower ? 0 : 1);
        }
        //  e_n(xi,yi) = si n < xi => P_n sino (yi*2,7)^(-(n-xi) / 25)
        System.out.println("Uso la exp");
        return pos;
    }

    /**
     * @return Analysis result
     */
    public AnalysisResult calculateParameters() {
        AnalysisResult result = new AnalysisResult();
        result.setAod(this.calculateAOD());
        result.setIar(this.calculateIAR());
        result.setAos(this.calculateAOS());
        result.setT(this.calculateT());

        result.setHeartRate((this.heartRate > 0) ? ((60 * 1000) / (this.heartRate * this.k.floatValue())) : 0f);
        return result;
    }
}
